/*
 * Copyright (c) 2009-2011, okazoh_tk. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the copyright holder nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "./DirectX9Private.h"

#include "alkes/directx9/DirectXGraphics.h"
#include "alkes/directx9/DirectXDecoderFactory.h"
#include "alkes/directx9/DirectXImageBuffer.h"
#include "alkes/directx9/DirectXRenderTarget.h"
#include "alkes/directx9/DirectXVertexBuffer.h"
#include "alkes/directx9/DirectXImageShape.h"
#include "alkes/math/Math.h"
#include "alkes/media/ImageWriter.h"

#define LOAD_MESSAGE()  \
    LOGI("Try creating GraphicsDevice from \"%s\".", __FUNC_NAME__)

AL_DX9_EXPORT_API alkes::IGraphicsDevice* alkes_create_graphics(void* handle, uint16_t width, uint16_t height)
{
    LOAD_MESSAGE();
    return alkes::DirectXGraphics::create((HWND)handle);
}

namespace alkes {

DirectXGraphics* DirectXGraphics::create(
    HWND hwnd,
    IDirect3D* dx,
    IDirect3DDevice* device)
{
    DirectXGraphics* gl = 0;

    bool destroy_dx = false;
    bool destroy_device = false;

    if (NULL == dx)
    {
        dx = Direct3DCreate(D3D_SDK_VERSION);
        destroy_dx = true;
    }
    else
    {
        dx->AddRef();
    }

    if (NULL == dx)
    {
        return 0;
    }

    D3DPRESENT_PARAMETERS param;

    if (NULL == device)
    {
        D3DDISPLAYMODE        dmode;

        AL_DX9_INVOKE(dx->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dmode));
        ZeroMemory(&param, sizeof(param));
        param.Windowed = TRUE;
        param.SwapEffect = D3DSWAPEFFECT_DISCARD;
//        param.SwapEffect = D3DSWAPEFFECT_FLIP;
//        param.SwapEffect = D3DSWAPEFFECT_COPY;
        param.BackBufferFormat = dmode.Format;
        param.BackBufferCount = 1;
        param.EnableAutoDepthStencil = TRUE;
        param.AutoDepthStencilFormat = D3DFMT_D16;
#if defined(ALKES_VALIDATE_ANTIALIAS)
        param.MultiSampleType = D3DMULTISAMPLE_NONMASKABLE;
        param.MultiSampleQuality = 1;
#endif

        if (!FAILED(AL_DX9_INVOKE(dx->CreateDevice(
            D3DADAPTER_DEFAULT,
            D3DDEVTYPE_HAL,
            hwnd,
            D3DCREATE_SOFTWARE_VERTEXPROCESSING,
            &param,
            &device))))
        {
            destroy_device = true;
        }
        else
        {
            device = NULL;
        }
    }
//    if (NULL != device)
    else
    {
        IDirect3DSwapChain* chain;
        if (!FAILED(AL_DX9_INVOKE(device->GetSwapChain(0, &chain))))
        {
            if (!FAILED(AL_DX9_INVOKE(chain->GetPresentParameters(&param))))
            {
                device->AddRef();
            }
            else
            {
                device = NULL;
            }
            chain->Release();
        }
        else
        {
            device = NULL;
        }
    }

    if (NULL == device)
    {
        dx->Release();

        AL_ASSERT(NULL == device);

        return 0;
    }

    gl = new DirectXGraphics(hwnd, dx, destroy_dx, device, destroy_device, param);

    if (!gl)
    {
        device->Release();
        dx->Release();
    }

    return gl;
}

void DirectXGraphics::destroy()
{
    delete this;
}

#define BITS_DESTROY_DX     (0x01)
#define BITS_DESTROY_DEVICE (0x02)

DirectXGraphics::DirectXGraphics(
    HWND hwnd, IDirect3D* dx, bool destroy_dx, IDirect3DDevice* device, bool destroy_device, const D3DPRESENT_PARAMETERS& param)
: hwnd_(hwnd)
, state_(device)
, flags_((destroy_dx ? BITS_DESTROY_DX : 0) | (destroy_device ? BITS_DESTROY_DEVICE : 0))
, dx_(dx)
, dxdevice_(device)
, default_target_(0)
, params_(param)
, scene_depth_(0)
{
    setAttribute(CORRECT_TEXEL_MAPPING, true);

    //printDeviceCaps(device);

    AL_DX9_INVOKE(D3DXCreateMatrixStack(0, &matrix_stack_));
    matrix_stack_->LoadIdentity();

    D3DVIEWPORT9 dxviewport;
    AL_DX9_INVOKE(device->GetViewport(&dxviewport));
    Viewport viewport = {
        (uint16_t)dxviewport.X,
        (uint16_t)dxviewport.Y,
        (uint16_t)dxviewport.Width,
        (uint16_t)dxviewport.Height
    };
    setViewport(viewport);

    // set default state
    state_.setRenderState(AL_DX_RS_CULLMODE, D3DCULL_CCW);

    LOGI("Load alkes DirectX 9 driver!!");
}

DirectXGraphics::~DirectXGraphics()
{
    if (matrix_stack_)
    {
        matrix_stack_->Release();
        matrix_stack_ = 0;
    }

    if (default_target_)
    {
        default_target_->release();
    }

    if (flags_ & BITS_DESTROY_DEVICE)
    {
        dxdevice_->Release();
    }

    if (flags_ & BITS_DESTROY_DX)
    {
        dx_->Release();
    }
}

void DirectXGraphics::onSnapshot(ImageWriter& writer, const Rect& rect)
{
    DirectXRenderTarget* render_target = restrict_cast<DirectXRenderTarget*>(getRenderTarget());

    if (!render_target)
    {
        LOGE("Unknown render target.(0x%08x)", render_target);
        return;
    }

    IDirect3DSurface* surface;
    if (FAILED(AL_DX9_INVOKE(dxdevice_->GetRenderTarget(0, &surface))))
    {
        LOGE("IDirect3DSurface is invalid.(0x%08x)", render_target);
        return;
    }

    const PixelFormatTraits* traits;
    D3DSURFACE_DESC desc;
    {
        if (FAILED(surface->GetDesc(&desc))
            || 0 == (traits = dxformat2traits(desc.Format)))
        {
            AL_ASSERT(false);
            surface->Release();
            return ;
        }
    }

    D3DLOCKED_RECT locked;
    RECT lock_rect;
    lock_rect.left = rect.x;
    lock_rect.top = rect.y;
    lock_rect.right = rect.x + rect.width;
    lock_rect.bottom = rect.y + rect.height;

    if (!FAILED(AL_DX9_INVOKE(surface->LockRect(&locked, &lock_rect, D3DLOCK_READONLY))))
    {
        writer.write(traits, locked.pBits, locked.Pitch, rect.width, rect.height);
        surface->UnlockRect();
    }

    surface->Release();
}

void DirectXGraphics::resize(uint16_t width, uint16_t height)
{
    if (params_.BackBufferWidth == width
        && params_.BackBufferHeight == height)
        return ;

    params_.BackBufferWidth = width;
    params_.BackBufferHeight = height;

    AL_DX9_INVOKE(dxdevice_->Reset(&params_));

    if (default_target_)
    {
        Viewport viewport = {
            (uint16_t)0,
            (uint16_t)0,
            (uint16_t)width,
            (uint16_t)height
        };
        setViewport(viewport);
        default_target_->onResetDevice();
    }

    if (hwnd_)
        InvalidateRect(hwnd_, NULL, FALSE);
}

void DirectXGraphics::flip()
{
    dxdevice_->Present(NULL, NULL, NULL, NULL);

    // TODO: Lost device

    if (hwnd_)
        ValidateRect(hwnd_ , NULL);
}


RenderTarget* DirectXGraphics::createDefaultRenderTarget()
{
    if (default_target_)
        default_target_->addRef();
    else
    {
        default_target_ = DirectXRenderTarget::createBackSurface(*this);
        default_target_->addRef();
    }

    return default_target_;
}

IVertexBuffer* DirectXGraphics::createVertexBuffer(uint32_t vertex_type)
{
    return DirectXVertexBuffer::create(this, vertex_type);
}

DecoderFactory* DirectXGraphics::createDecoderFactory()
{
    DirectXDecoderFactory* factory = 0;
    DirectXImageBufferFactory* ibfactory = DirectXImageBufferFactory::create(this->getDevice());

    if (ibfactory)
    {
        factory = DirectXDecoderFactory::create(ibfactory);
    }

    return factory;
}

ImageBufferFactory* DirectXGraphics::createImageBufferFactory()
{
    return DirectXImageBufferFactory::create(this->getDevice());
}

ImageShape* DirectXGraphics::createImageShape(uint32_t num)
{
    return DirectXImageShape::create(this, num);
}


////////////////////////////////////////////////////////////////
// Rendering state


void DirectXGraphics::beginScene()
{
    if (!scene_depth_)
    {
        dxdevice_->BeginScene();
    }

    ++scene_depth_;
}

void DirectXGraphics::endScene()
{
    --scene_depth_;

    if (!scene_depth_)
    {
        dxdevice_->EndScene();
    }
}

void DirectXGraphics::beginDraw2D(RenderTarget* target)
{
    setRenderTarget(target);

    pushMatrix();

    bool is_2d_scene = !scene_depth_;

    beginScene();

    setState(AL_GS_SCISSOR_TEST, true);
    setState(AL_GS_LIGHTING, false);
    setState(AL_GS_LINE_SMOOTH, false);
    // set default state

#if defined(ALKES_VALIDATE_ANTIALIAS)
    state_.setRenderState(AL_DX9_ST_MULTISAMPLEANTIALIAS, true);
#endif

    // set matrix
    D3DXMATRIX ortho;

    D3DXMatrixOrthoOffCenterRH(
        &ortho,
        (float)viewport_.x, (float)viewport_.x + viewport_.w,
        (float)viewport_.y + viewport_.h, (float)viewport_.y,
        0.0f, 1.0f);

    if (is_2d_scene)
    {
        dxdevice_->SetTransform(D3DTS_PROJECTION, &ortho);
    }
    else
    {
        matrix_stack_->MultMatrix(&ortho);
//        D3DXMatrixIdentity(&ortho);
    }
}

void DirectXGraphics::endDraw2D()
{
    endScene();

    popMatrix();
}


////////////////////////////////////////////////////////////////
// Clear buffer

void DirectXGraphics::clearBuffer(uint32_t buffer_flag, const Color& clear_color, float depth)
{
    if (!buffer_flag)
        return ;

    DWORD flag = 0;
    DWORD color = 0;

    if (buffer_flag & AL_BUFFER_COLOR)
    {
        flag |= D3DCLEAR_TARGET;
        color = D3DCOLOR_ARGB(clear_color.a, clear_color.r, clear_color.g, clear_color.b);
    }

    if (buffer_flag & AL_BUFFER_DEPTH)
    {
        flag |= D3DCLEAR_ZBUFFER;
    }

    if (flag)
    {
        RECT dst;
        D3DRECT rect;
        AL_DX9_INVOKE(dxdevice_->GetScissorRect(&dst));

        rect.x1 = dst.left;
        rect.y1 = dst.top;
        rect.x2 = dst.right;
        rect.y2 = dst.bottom;

        AL_DX9_INVOKE(dxdevice_->Clear(1, &rect, flag, color, depth, 0));
    }
}


////////////////////////////////////////////////////////////////
// Scissor rect and viewport

void DirectXGraphics::setScissorRect(const Rect& rect)
{
    RECT dxrect;

    dxrect.left = rect.x + viewport_.x;
    dxrect.right = rect.x + rect.width;
    dxrect.top = rect.y + viewport_.y;
    dxrect.bottom = rect.y + rect.height;

    dxdevice_->SetScissorRect(&dxrect);
}

const Rect DirectXGraphics::getScissorRect() const
{
    RECT dxrect;
    Rect rect;
    dxdevice_->GetScissorRect(&dxrect);

    rect.x = (Rect::value_type)dxrect.left - (Rect::value_type)viewport_.x;
    rect.y = (Rect::value_type)dxrect.top - (Rect::value_type)viewport_.y;
    rect.width = (Rect::value_type)(dxrect.right - dxrect.left);
    rect.height = (Rect::value_type)(dxrect.bottom - dxrect.top);

    return rect;
}

void DirectXGraphics::setViewport(const Viewport& viewport)
{
    if (viewport_ != viewport)
    {
        viewport_ = viewport;
        D3DVIEWPORT9 tmp = {viewport.x, viewport.y, viewport.w, viewport.h, 0.0f, 1.0f};
        dxdevice_->SetViewport(&tmp);
    }
}

const Viewport DirectXGraphics::getViewport() const
{
    return viewport_;
}

void DirectXGraphics::setPerspective(float angle, float aspect, float znear, float zfar)
{
    D3DXMATRIX matrix;
    D3DXMatrixPerspectiveFovRH(
        &matrix,
        D3DXToRadian(angle),
        aspect,
        znear,
        zfar);
    dxdevice_->SetTransform(D3DTS_PROJECTION , &matrix);
}

void DirectXGraphics::setLookAt(const Vector3D& eye, const Vector3D& at, const Vector3D& up)
{
    D3DXMATRIX matrix;
    D3DXVECTOR3 e(eye.x, eye.y, eye.z);
    D3DXVECTOR3 a(at.x, at.y, at.z);
    D3DXVECTOR3 u(up.x, up.y, up.z);
    D3DXMatrixLookAtRH(&matrix, &e, &a, &u);
    dxdevice_->SetTransform(D3DTS_VIEW , &matrix);
}


////////////////////////////////////////////////////////////////
// Alpha blend configuration

const static D3DBLEND s_blend_func_al2dx[] = {
    D3DBLEND_ZERO,
    D3DBLEND_ONE,
    D3DBLEND_SRCCOLOR,
    D3DBLEND_INVSRCCOLOR,
    D3DBLEND_DESTCOLOR,
    D3DBLEND_INVDESTCOLOR,
    D3DBLEND_SRCALPHA,
    D3DBLEND_INVSRCALPHA,
    D3DBLEND_DESTALPHA,
    D3DBLEND_INVDESTALPHA,
};

const static AlphaBlendMode s_blend_func_dx2al[] = {
    (AlphaBlendMode)-1,
    AL_BLEND_ZERO,
    AL_BLEND_ONE,
    AL_BLEND_SRCCOLOR,
    AL_BLEND_INVSRCCOLOR,
    AL_BLEND_SRCALPHA,
    AL_BLEND_INVSRCALPHA,
    AL_BLEND_DSTALPHA,
    AL_BLEND_INVDSTALPHA,
    AL_BLEND_DSTCOLOR,
    AL_BLEND_INVDSTCOLOR,
};

void DirectXGraphics::setState(int state, bool enable)
{
    switch (state)
    {
    case AL_GS_ALPHA_BLEND:
        state_.setRenderState(AL_DX_RS_ALPHABLENDENABLE, enable);
        break;
    case AL_GS_LINE_SMOOTH:
        state_.setRenderState(AL_DX_RS_ANTIALIASEDLINEENABLE, enable);
        break;
    case AL_GS_DEPTH_TEST:
        state_.setRenderState(AL_DX_RS_ZENABLE, enable ? D3DZB_TRUE : D3DZB_FALSE);
        state_.setRenderState(AL_DX_RS_ZWRITEENABLE, enable);
        break;
    case AL_GS_CULL_FACE:
        state_.setRenderState(AL_DX_RS_CULLMODE, enable ? D3DCULL_CCW : D3DCULL_NONE);
        break;
    case AL_GS_SCISSOR_TEST:
        state_.setRenderState(AL_DX_RS_SCISSORTESTENABLE, enable);
        break;
    case AL_GS_STENCIL_TEST:
        state_.setRenderState(AL_DX_RS_STENCILENABLE, enable);
        break;
    case AL_GS_ALPHA_TEST:
        state_.setRenderState(AL_DX_RS_ALPHATESTENABLE, enable);
        break;
    case AL_GS_LIGHTING:
        state_.setRenderState(AL_DX_RS_LIGHTING, enable);
        break;
    case AL_GS_MULTISAMPLE:
        state_.setRenderState(AL_DX_RS_MULTISAMPLEANTIALIAS, enable);
        break;

    default:
        AL_NO_SUPPORT("%s state=%d", __FUNC_NAME__, state);
        break;
    }
}

bool DirectXGraphics::getState(int state)
{
    bool ret = false;
    switch (state)
    {
    case AL_GS_ALPHA_BLEND:
        ret = state_.getRenderState(AL_DX_RS_ALPHABLENDENABLE);
        break;
    case AL_GS_LINE_SMOOTH:
        ret = state_.getRenderState(AL_DX_RS_ANTIALIASEDLINEENABLE);
        break;
    case AL_GS_DEPTH_TEST:
        ret = state_.getRenderState(AL_DX_RS_ZENABLE) == D3DZB_FALSE ? false : true;
        break;
    case AL_GS_CULL_FACE:
        ret = state_.getRenderState(AL_DX_RS_CULLMODE) == D3DCULL_NONE ? false : true;
        break;
    case AL_GS_SCISSOR_TEST:
        ret = state_.getRenderState(AL_DX_RS_SCISSORTESTENABLE);
        break;
    case AL_GS_STENCIL_TEST:
        ret = state_.getRenderState(AL_DX_RS_STENCILENABLE);
        break;
    case AL_GS_ALPHA_TEST:
        ret = state_.getRenderState(AL_DX_RS_ALPHATESTENABLE);
        break;
    case AL_GS_LIGHTING:
        ret = state_.getRenderState(AL_DX_RS_LIGHTING);
        break;
    case AL_GS_MULTISAMPLE:
        ret = state_.getRenderState(AL_DX_RS_MULTISAMPLEANTIALIAS);
        break;
    default:
        AL_NO_SUPPORT("%s state=%d", __FUNC_NAME__, state);
        break;
    }
    return ret;
}

void DirectXGraphics::setAlphaBlendMode(AlphaBlendMode src_blend, AlphaBlendMode dst_blend)
{
    if (AL_LENGTH(s_blend_func_al2dx) <= src_blend
        || AL_LENGTH(s_blend_func_al2dx) <= dst_blend)
        return ; // invalid parameter

    state_.setRenderState(AL_DX_RS_SRCBLEND, s_blend_func_al2dx[src_blend]);
    state_.setRenderState(AL_DX_RS_DESTBLEND, s_blend_func_al2dx[dst_blend]);
}

void DirectXGraphics::getAlphaBlendMode(AlphaBlendMode* src_blend, AlphaBlendMode* dst_blend) const
{
    DWORD value;
    if (src_blend)
    {
        value = state_.getRenderState(AL_DX_RS_SRCBLEND);
        *src_blend = s_blend_func_dx2al[value];
    }

    if (dst_blend)
    {
        value = state_.getRenderState(AL_DX_RS_DESTBLEND);
        *src_blend = s_blend_func_dx2al[value];
    }
}


////////////////////////////////////////////////////////////////
// Texture configuration

bool DirectXGraphics::setTexture(Image* image, int stage)
{
    AL_PRE_COND(0 <= stage && stage < AL_LENGTH(textures_), false);
    ImageBuffer* texture = image ? image->getImageBuffer() : 0;

    if (!textures_[stage].updateTexture(texture))
    {
        // Do nothing.
        return true;
    }

    IDirect3DTexture* tex = 0;
    if (texture)
    {
        DirectXImageBuffer* dxib = restrict_cast<DirectXImageBuffer*>(texture);
        tex = dxib ? dxib->getTexture() : 0;
    }

    dxdevice_->SetTexture(stage, tex);

    if (tex)
    {
        dxdevice_->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, stage);

        if (AL_PIXEL_FORMAT_A8 == texture->getPixelFormat())
        {
            dxdevice_->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
            dxdevice_->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_DIFFUSE);
            dxdevice_->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
        }
        else
        {
            dxdevice_->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
            dxdevice_->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
            dxdevice_->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
        }
        dxdevice_->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
        dxdevice_->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
        dxdevice_->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);
    }

    return true;
}

void DirectXGraphics::setTextureFilter(TexFilterValue min_filter, TexFilterValue mag_filter, int stage)
{
    static const D3DTEXTUREFILTERTYPE filter_types[] = {
        D3DTEXF_POINT,
        D3DTEXF_LINEAR,
    };

    AL_PRE_COND(0 <= min_filter && min_filter < AL_LENGTH(filter_types)
        && 0 <= mag_filter && mag_filter < AL_LENGTH(filter_types), );

    // mag filter
    if (textures_[stage].updateMagFilter(mag_filter))
    {
        dxdevice_->SetSamplerState((DWORD)stage, D3DSAMP_MAGFILTER, filter_types[mag_filter]);
    }

    // min filter
    if (textures_[stage].updateMinFilter(min_filter))
    {
        dxdevice_->SetSamplerState((DWORD)stage, D3DSAMP_MINFILTER, filter_types[min_filter]);
    }
}

void DirectXGraphics::getTextureFilter(TexFilterValue* min_filter, TexFilterValue* mag_filter, int stage)
{
    if (mag_filter)
    {
        *min_filter = textures_[stage].getMagFilter();
    }

    if (min_filter)
    {
        *min_filter = textures_[stage].getMagFilter();
    }
}

void DirectXGraphics::setTextureAddressMode(TexAddressMode u_mode, TexAddressMode v_mode, int stage)
{
    static const D3DTEXTUREADDRESS addr_modes[] = {
        D3DTADDRESS_BORDER,
        D3DTADDRESS_WRAP,
        D3DTADDRESS_CLAMP,
    };

    AL_PRE_COND(0 <= u_mode && u_mode < AL_LENGTH(addr_modes)
        && 0 <= v_mode && v_mode < AL_LENGTH(addr_modes), );

    // u
    if (textures_[stage].updateAddressU(u_mode))
    {
        dxdevice_->SetSamplerState((DWORD)stage, D3DSAMP_ADDRESSU, addr_modes[u_mode]);
    }

    // v
    if (textures_[stage].updateAddressV(v_mode))
    {
        dxdevice_->SetSamplerState((DWORD)stage, D3DSAMP_ADDRESSV, addr_modes[v_mode]);
    }
}

void DirectXGraphics::getTextureAddressMode(TexAddressMode* u_mode, TexAddressMode* v_mode, int stage)
{
    if (u_mode)
        *u_mode = textures_[stage].getAddressU();

    if (v_mode)
        *v_mode = textures_[stage].getAddressV();
}


////////////////////////////////////////////////////////////////
// Primitive rendering

static UINT get_pointlist_count(uint32_t count) { return count; }
static UINT get_linelist_count(uint32_t count) { return count / 2; }
static UINT get_linestrip_count(uint32_t count) { return count - 1; }
static UINT get_trianglestrip_count(uint32_t count) { return count - 2; }
static UINT get_trianglefan_count(uint32_t count) { return count - 2; }
static UINT get_trianglelist_count(uint32_t count) { return count / 3; }

static struct {
    D3DPRIMITIVETYPE type;
    UINT (*convert)(uint32_t);
} s_primitive_types[] = {
    {D3DPT_POINTLIST,       get_pointlist_count},
    {D3DPT_LINELIST,        get_linelist_count},
    {D3DPT_LINESTRIP,       get_linestrip_count},
    {D3DPT_TRIANGLESTRIP,   get_trianglestrip_count},
    {D3DPT_TRIANGLEFAN,     get_trianglefan_count},
    {D3DPT_TRIANGLELIST,    get_trianglelist_count},
};

bool  DirectXGraphics::drawPrimitive(IVertexBuffer* vbuffer, PrimitiveType type, uint32_t start, uint32_t vertex_count)
{
    DirectXVertexBuffer* vb = restrict_cast<DirectXVertexBuffer*>(vbuffer);
    if (!vb)
        return false;

    AL_DX9_INVOKE(dxdevice_->SetTransform(D3DTS_WORLDMATRIX(0), matrix_stack_->GetTop()));
    AL_DX9_INVOKE(dxdevice_->SetFVF(vb->getFVF()));

    if (type < AL_LENGTH(s_primitive_types))
    {
        AL_DX9_INVOKE(dxdevice_->SetStreamSource(0, vb->getIDirect3DVertexBuffer(), 0, vb->getVertexSize()));
        AL_DX9_INVOKE(dxdevice_->DrawPrimitive(s_primitive_types[type].type, start, s_primitive_types[type].convert(vertex_count)));
    }
    else
    {
        if (type == AL_PRIM_LINE_LOOP)
        {
            uint16_t indeces[] = {0,1,2,3,0};

            AL_DX9_INVOKE(dxdevice_->DrawIndexedPrimitiveUP(
                D3DPT_LINESTRIP,
                0, vertex_count, vertex_count,
                indeces, D3DFMT_INDEX16,
                vb->getBuffer(start, vertex_count), vb->getVertexSize()));
        }
        else
        {
            AL_DX9_INVOKE(dxdevice_->SetStreamSource(0, vb->getIDirect3DVertexBuffer(), 0, vb->getVertexSize()));

            uint32_t offset;
            D3DPRIMITIVETYPE prim_type;
            if (type == AL_PRIM_QUAD_STRIP)
            {
                offset = 2;
                prim_type = D3DPT_TRIANGLESTRIP;
            }
            else if (type == AL_PRIM_QUADS)
            {
                offset = 4;
                prim_type = D3DPT_TRIANGLEFAN;
            }
            else
                return false;

            uint32_t i;
            for (i = 0; i < vertex_count / offset; ++i)
            {
                AL_DX9_INVOKE(dxdevice_->DrawPrimitive(prim_type, start + i * offset, 2));
            }
        }
    }

    return true;
}

////////////////////////////////////////////////////////////////
// Transformation

void DirectXGraphics::pushMatrix()
{
    if (matrix_stack_)
        matrix_stack_->Push();
}

void DirectXGraphics::popMatrix()
{
    if (matrix_stack_)
        matrix_stack_->Pop();
}

void DirectXGraphics::translate(float x, float y)
{
    translate(x, y, 0.0f);
}

void DirectXGraphics::translateForImage(float x, float y)
{
    translate(x - 0.5f, y - 0.5f);
}

void DirectXGraphics::scale(float xscale, float yscale, const Vector2D& origin)
{
    if (matrix_stack_)
    {
        D3DXMATRIX scaling;

        D3DXMatrixIdentity(&scaling);
        scaling.m[0][0] = xscale;
        scaling.m[1][1] = yscale;

        if (origin != Vector2D::ZERO)
        {
            scaling.m[3][0] = (1-xscale) * origin.x;
            scaling.m[3][1] = (1-yscale) * origin.y;
        }

        matrix_stack_->MultMatrixLocal(&scaling);
    }
}

void DirectXGraphics::rotate(float degrees, const Vector2D& origin)
{
    if (matrix_stack_)
    {
        D3DXMATRIX rotation;
        float a = (360.f - degrees) * (float)PI / 180.f;
        float c = cosf(a);
        float s = sinf(a);

        D3DXMatrixIdentity(&rotation);
        rotation.m[0][0] = c; rotation.m[0][1] = -s;
        rotation.m[1][0] = s; rotation.m[1][1] =  c;

        if (origin != Vector2D::ZERO)
        {
            rotation.m[3][0] = (1 - c) * origin.x - s * origin.y;
            rotation.m[3][1] = (1 - c) * origin.y + s * origin.x;
        }

        matrix_stack_->MultMatrixLocal(&rotation);
    }
}

void DirectXGraphics::skewX(float angle)
{
    if (matrix_stack_)
    {
        D3DXMATRIX sk;
        float tx = tan(angle * (float)PI / 180.f);

        D3DXMatrixIdentity(&sk);

        sk.m[1][0] = tx;

        matrix_stack_->MultMatrixLocal(&sk);
    }
}

void DirectXGraphics::skewY(float angle)
{
    if (matrix_stack_)
    {
        D3DXMATRIX sk;
        float ty = tan(angle * (float)PI / 180.f);

        D3DXMatrixIdentity(&sk);

        sk.m[0][1] = ty;

        matrix_stack_->MultMatrixLocal(&sk);
    }
}

void DirectXGraphics::translate(float x, float y, float z)
{
    if (matrix_stack_)
    {
        matrix_stack_->TranslateLocal(x, y, z);
    }
}

void DirectXGraphics::scale(float xscale, float yscale, float zscale)
{
    if (matrix_stack_)
    {
        D3DXMATRIX scaling;
        D3DXMatrixScaling(&scaling, xscale, yscale, zscale);
        matrix_stack_->MultMatrixLocal(&scaling);
    }
}

void DirectXGraphics::rotate(float angle, float x, float y, float z)
{
    if (matrix_stack_)
    {
        D3DXMATRIX rot;
        D3DXVECTOR3 v(x, y, z);
        D3DXMatrixRotationAxis(&rot, &v, D3DXToRadian(angle));
        matrix_stack_->MultMatrixLocal(&rot);
    }
}

Shader* DirectXGraphics::createVertexShader(const char* source)
{
    // FIXME
    AL_VERIFY(false);
    return 0;
}

Shader* DirectXGraphics::createFragmentShader(const char* source)
{
    // FIXME
    AL_VERIFY(false);
    return 0;
}

Program* DirectXGraphics::createShaderProgram(Shader* vertex_shader, Shader* fragment_shader)
{
    // FIXME
    AL_VERIFY(false);
    return 0;
}

bool DirectXGraphics::setShaderProgram(Program* shader_program)
{
    // FIXME
    AL_VERIFY(false);
    return false;
}

}
